/*
 * Following codes are derived from 
 * ``Secure Programming Cookbook for C and C++''.
 * URL:http://www.oreilly.com/catalog/secureprgckbk/ 
 *     http://www.secureprogramming.com/
 * 
 * Licensing Information can be obtain from following URL:
 *
 *  http://www.secureprogramming.com/?action=license
 *
 * Copyright  2003 by John Viega and Matt Messier.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 

 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the developer of this software nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <openssl/evp.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include "common.h"

static unsigned long key_select_table[]={
  IPMSG_BLOWFISH_256,  
  IPMSG_RC2_256,
  IPMSG_BLOWFISH_128,
  IPMSG_RC2_128,
  IPMSG_RC2_40,
  -1,
};

int
symcrypt_get_skey_length(unsigned long type,size_t *ret_len){
  size_t key_len;

  if (!ret_len)
    return -EINVAL;

  switch(type){
  case IPMSG_RC2_40:
    key_len=5;
    break;
  case IPMSG_RC2_128:
  case IPMSG_BLOWFISH_128:
    key_len=16;
    break;
  case IPMSG_RC2_256:
  case IPMSG_BLOWFISH_256:
    key_len=32;
    break;
  default:
    return -EINVAL;
    break;
  }
  *ret_len=key_len;

  return 0;
}

int
blowfish_cbc_encrypt_setup(const char *key,size_t key_len_byte,const char *iv,EVP_CIPHER_CTX **ret){
  EVP_CIPHER_CTX *ctx=NULL;

  if (!ret)
    return -EINVAL;

  ctx = (EVP_CIPHER_CTX *)g_malloc(sizeof(EVP_CIPHER_CTX));
  if (!ctx)
    return -ENOMEM;

  EVP_CIPHER_CTX_init(ctx);
  
  EVP_EncryptInit(ctx, EVP_bf_cbc(), NULL,NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_len_byte);
  EVP_EncryptInit(ctx, NULL, key, iv);
  *ret=ctx;

  return 0;
}
int
blowfish_cbc_decrypt_setup(const char *key,size_t key_len_byte,const char *iv,EVP_CIPHER_CTX **ret){
  EVP_CIPHER_CTX *ctx=NULL;

  if (!ret)
    return -EINVAL;

  ctx = (EVP_CIPHER_CTX *)g_malloc(sizeof(EVP_CIPHER_CTX));
  if (!ctx)
    return -ENOMEM;

  EVP_CIPHER_CTX_init(ctx);
  
  EVP_DecryptInit(ctx, EVP_bf_cbc(), NULL, NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_len_byte);
  EVP_DecryptInit(ctx, NULL, key, iv);
  *ret=ctx;

  return 0;
}

int
rc2_cbc_encrypt_setup(const char *key,size_t key_len_byte,const char *iv,EVP_CIPHER_CTX **ret){
  EVP_CIPHER_CTX *ctx=NULL;

  if (!ret)
    return -EINVAL;

  ctx = (EVP_CIPHER_CTX *)g_malloc(sizeof(EVP_CIPHER_CTX));
  if (!ctx)
    return -ENOMEM;

  EVP_CIPHER_CTX_init(ctx);
  
  EVP_EncryptInit(ctx, EVP_rc2_cbc(), NULL,NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_len_byte);
  EVP_EncryptInit(ctx, NULL, key, iv);
  *ret=ctx;

  return 0;
}
int
rc2_cbc_decrypt_setup(const char *key,size_t key_len_byte,const char *iv,EVP_CIPHER_CTX **ret){
  EVP_CIPHER_CTX *ctx=NULL;

  if (!ret)
    return -EINVAL;

  ctx = (EVP_CIPHER_CTX *)g_malloc(sizeof(EVP_CIPHER_CTX));
  if (!ctx)
    return -ENOMEM;

  EVP_CIPHER_CTX_init(ctx);
  
  EVP_DecryptInit(ctx, EVP_rc2_cbc(), NULL, NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_len_byte);
  EVP_DecryptInit(ctx, NULL, key,iv);
  *ret=ctx;

  return 0;
}

int
common_cbc_encrypt(EVP_CIPHER_CTX *ctx, const char *data, int inl, int *rb,char **ret_buff) {
  int  i, ol, tmp;
  char *ret;

  if ( (!ctx) || (!data) || (!ret_buff) )
    return -EINVAL;
  dbg_out("encrypt inl:%d\n", inl);
  ol = 0;
  if (!(ret = (char *)g_malloc(i = inl + EVP_CIPHER_CTX_block_size(ctx)))) 
    g_assert_not_reached();

  if (i < inl)
    g_assert_not_reached();

  for (i = 0;  i < inl / 100;  i++) {
    if (!EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[i*100], 100)) 
      g_assert_not_reached();

    ol += tmp;
  }
  if (inl % 100) {
    if (!EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[(inl/100)*100], inl % 100)) 
      g_assert_not_reached();
    ol += tmp;
  }
  dbg_out("Encrypt update %d length\n",ol);
  if (!EVP_EncryptFinal_ex(ctx, &ret[ol], &tmp)) 
    g_assert_not_reached();

  ol += tmp;
  dbg_out("Encrypt final %d length\n",ol);
  if (rb) 
    *rb = ol;

  *ret_buff=ret;
  return 0;
}

int 
common_cbc_decrypt(EVP_CIPHER_CTX *ctx, const char *ct, int inl,char **ret_buff,size_t *outl) {
  int rc;
  int  ol,i,tlen;
  char *pt;
  char errbuf[1024];

  if ( (!ctx) || (!ct) || (!ret_buff) || (!outl) )
    return -EINVAL;

  i = inl + EVP_CIPHER_CTX_block_size(ctx) + 1;
  pt = (char *)g_malloc(i);
  if (!pt) 
    g_assert_not_reached();

  if (i < inl) 
    g_assert_not_reached();
  dbg_out("Decrypt try to :%d length\n",inl);
  rc=EVP_DecryptUpdate(ctx, pt, &ol, ct, inl);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not decrypt update message err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    g_free(pt);
    return -ENOENT;
  }
  dbg_out("Decrypt update %d length\n",ol);
  rc=EVP_DecryptFinal (ctx, pt + ol, &tlen);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not decrypt finalize message err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    g_free(pt);
    return -ENOENT;
  }
  ol+=tlen;
  pt[ol]=0;

  *ret_buff=pt;
  *outl=ol;

  return 0;
}

int
common_cbc_finalize(EVP_CIPHER_CTX **ctx_p) {
  EVP_CIPHER_CTX *ctx;

  if ( (!ctx_p) || ( !(*ctx_p) ) )
    return -EINVAL;

  ctx=*ctx_p;

  EVP_CIPHER_CTX_cleanup(ctx);
  g_free(ctx);

  *ctx_p=NULL;

  return 0;
}

int
symcrypt_encrypt_message(unsigned long type,const unsigned char *plain,
			 char **key_p, size_t *key_len_p,char **enc_p,
			 size_t *enc_len_p){
  size_t key_len;
  size_t enc_len;
  EVP_CIPHER_CTX *ctx=NULL;
  char *key;
  char iv[EVP_MAX_IV_LENGTH];
  char *enc=NULL;
  int rc;

  if ( (!plain) || (!key_p) || (!key_len_p) || (!enc_p) || (!enc_len_p) )
    return -EINVAL;

  rc=symcrypt_get_skey_length(type, &key_len);
  if (rc)
    return rc;

  key=g_malloc(key_len);
  if (!key)
    return -ENOMEM;
  
  rc=generate_rand(key,key_len);
  if (rc) 
    goto key_free_out;

  memset(iv,0,EVP_MAX_IV_LENGTH);

  rc=-EINVAL;
  switch(type){
  case IPMSG_RC2_40:
  case IPMSG_RC2_128:
  case IPMSG_RC2_256:
    rc=rc2_cbc_encrypt_setup(key,key_len,NULL,&ctx);
    break;
  case IPMSG_BLOWFISH_128:
  case IPMSG_BLOWFISH_256:
    rc=blowfish_cbc_encrypt_setup(key,key_len,NULL,&ctx);
    break;
  default:
    goto key_free_out;
    break;
  }
  if (rc) {
    err_out("Can not set key\n");
    goto key_free_out;
  }

  rc=common_cbc_finalize(&ctx);
  if (rc) {
    err_out("Can not clean up encrypt\n");
    goto enc_free_out;
  }
  *key_p=key;
  *key_len_p=key_len;
  *enc_p=enc;
  *enc_len_p=enc_len;
  return 0;
 enc_free_out:
  if (enc)
    g_free(enc);
 ctx_free_out:
  if (ctx)
    common_cbc_finalize(&ctx);
 key_free_out:
  if (key)
    g_free(key);

  return rc;
}
int
symcrypt_decrypt_message(unsigned long type,const unsigned char *encoded,
			 size_t enc_len, const char *key,char **dec_p,size_t *dec_len_p){
  size_t key_len;
  EVP_CIPHER_CTX *ctx=NULL;
  char *dec=NULL;
  char *new_dec=NULL;
  size_t dec_len;
  int rc;

  if ( (!encoded) || (!key) || (!dec_p) || (!dec_len_p) )
    return -EINVAL;

  rc=symcrypt_get_skey_length(type, &key_len);
  if (rc)
    return rc;

  rc=-EINVAL;
  switch(type){
  case IPMSG_RC2_40:
  case IPMSG_RC2_128:
  case IPMSG_RC2_256:
    dbg_out("Use RC2 key %d\n",key_len);
    rc=rc2_cbc_decrypt_setup(key,key_len,NULL,&ctx);
    break;
  case IPMSG_BLOWFISH_128:
  case IPMSG_BLOWFISH_256:
    dbg_out("Use blowfish key %d\n",key_len);
    rc=blowfish_cbc_decrypt_setup(key,key_len,NULL,&ctx);
    break;
  default:
    return -EINVAL;
    break;
  }
  dbg_out("Decrypt:%s (len=%d) into plain\n",encoded,enc_len);
  rc=common_cbc_decrypt(ctx, encoded, enc_len, &dec,&dec_len);
  if (rc) {
    err_out("Can not decrypt\n");
    goto ctx_free_out;
  }
  dbg_out("Decrypt len:%d\n",dec_len);
  rc=common_cbc_finalize(&ctx);
  if (rc) {
    err_out("Can not clean up decrypt\n");
    goto dec_free_out;
  }
  if (dec[dec_len-1] != '\0') {
    new_dec=g_malloc(dec_len+1);
    if (!new_dec)
      goto dec_free_out;
    memcpy(new_dec,dec,dec_len);
    new_dec[dec_len]='\0';
    g_free(dec);
    dec=new_dec;
  }
  *dec_p=dec;
  *dec_len_p=dec_len;
  dbg_out("decoded:%s %d\n",dec,dec_len);

  return 0;
 dec_free_out:
  if (dec)
    g_free(dec);
 ctx_free_out:
  if (ctx)
    common_cbc_finalize(&ctx);

  return rc;
}
int 
select_symmetric_key(unsigned long peer_cap,unsigned long *selected_key,int speed){
  unsigned long candidates;
  int i;
  int added_num=1;
  int first=0;
  int key_type;

  if (!selected_key)
    return -EINVAL;

  candidates=(get_symkey_part(peer_cap) & hostinfo_get_ipmsg_crypt_capability());
  if (speed) {
    first=SYMCRYPT_MAX_KEY_TYPE-1;
    added_num=-1;
  }
  for (i=first;
       ((i>=0) && (i<SYMCRYPT_MAX_KEY_TYPE) && (!(key_select_table[i] & candidates)));
       i+=added_num);

  if (!( (i>=0) && (i<SYMCRYPT_MAX_KEY_TYPE) ))
    return -ENOENT;

  *selected_key=key_select_table[i];
  dbg_out("Selected key name in ipmsg:0x%x\n",key_select_table[i]);

  return 0;
}

